#include "StdAfx.h"
#include "Graphics.h"
#include "App.h"

namespace Renderer
{

	Graphics::Graphics(HINSTANCE hInst, HWND hWnd, D3DDEVTYPE devType, DWORD requestedVP) : d3dObject(0)
	{
		this->hWnd = hWnd;
		this->hInst = hInst;
		this->devType = devType;
		this->requestedVP = requestedVP;
		this->d3dObject = NULL;
		this->d3dDevice = NULL;
		this->sprite = NULL;

		ZeroMemory(&d3dPP, sizeof(d3dPP));

		isHAL = false;

		ambientRed = 255;
		ambientGreen = 255;
		ambientBlue = 255;
		BPP = 0;
		width = height = 0;
		FPS = 0;
		frameCount = 0;
		fpsTimer = 0;

		InitD3D();
	}

	Graphics::~Graphics(void)
	{

		Shutdown();
	}


	bool Graphics::InitD3D()
	{
		Shutdown();

		d3dObject = Direct3DCreate9(D3D_SDK_VERSION);
		
		if(!d3dObject)
		{
			gApp->AppError(true, "Epic fail creating Direct3D object");
			PostQuitMessage(0);
			return false;
		}

		D3DDISPLAYMODE mode;

		//Get default display mode
		d3dObject->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &mode);

		//check if adapter supports default
		HR(d3dObject->CheckDeviceType(D3DADAPTER_DEFAULT, devType, mode.Format, mode.Format, true));
		//check if adapter supports 32bit display mode
		HR(d3dObject->CheckDeviceType(D3DADAPTER_DEFAULT, devType, D3DFMT_X8R8G8B8, D3DFMT_X8R8G8B8, false));


		//get device caps
		D3DCAPS9 caps;
		HR(d3dObject->GetDeviceCaps(D3DADAPTER_DEFAULT, devType, &caps));

		//check if supports hardware tranform and light
		DWORD devBehaviorFlags = 0;
		if(caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT)
			devBehaviorFlags |= requestedVP;
		else
			devBehaviorFlags |= D3DCREATE_SOFTWARE_VERTEXPROCESSING;

		//check if pure device and hardware vertex processing
		if(caps.DevCaps & D3DDEVCAPS_PUREDEVICE &&
			devBehaviorFlags & D3DCREATE_HARDWARE_VERTEXPROCESSING)
			devBehaviorFlags |= D3DCREATE_PUREDEVICE;

		d3dPP.BackBufferWidth = 0;
		d3dPP.BackBufferHeight = 0;
		d3dPP.BackBufferFormat = D3DFMT_UNKNOWN;
		d3dPP.BackBufferCount = 1;
		d3dPP.MultiSampleType = D3DMULTISAMPLE_NONE;
		d3dPP.MultiSampleQuality = 0;
		d3dPP.SwapEffect = D3DSWAPEFFECT_DISCARD;
		d3dPP.hDeviceWindow = hWnd;
		d3dPP.Windowed = true;
		d3dPP.EnableAutoDepthStencil = true;
		d3dPP.AutoDepthStencilFormat = D3DFMT_D24S8;
		d3dPP.Flags = 0;
		d3dPP.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
		d3dPP.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;

		HR(d3dObject->CreateDevice(
			D3DADAPTER_DEFAULT,
			devType,
			hWnd,
			devBehaviorFlags,
			&d3dPP,
			&d3dDevice));


		return true;
	}

	void Graphics::Resize(RECT & clientRect)
	{
		//d3dPP.BackBufferWidth  = clientRect.right;
		//d3dPP.BackBufferHeight = clientRect.bottom;
		//OnLostDevice();
		//HR(d3dDevice->Reset(&d3dPP));
		//OnResetDevice();

  
		// get the size of the containing window
		RECT rcWindow;
		GetWindowRect(hWnd, &rcWindow);
	  
		// determine the difference between the two
		POINT diff;
		diff.x = (rcWindow.right - rcWindow.left) - clientRect.right;
		diff.y = (rcWindow.bottom - rcWindow.top) - clientRect.bottom;
		// resize the client area
		MoveWindow(hWnd,rcWindow.left, rcWindow.top, width + diff.x, height + diff.y, TRUE);
	}

	int Graphics::Windowed() const
	{
		return d3dPP.Windowed;
	}

	void Graphics::SetBackBufferWidth(WORD width)
	{
		d3dPP.BackBufferWidth = width;
	}
	void Graphics::SetBackBufferHeight(WORD height)
	{
		d3dPP.BackBufferHeight = height;
	}

	void Graphics::OnResetDevice()
	{
		HR(d3dDevice->Reset(&d3dPP));
		sprite->OnResetDevice();
		font.OnResetDevice();
	}

	void Graphics::OnLostDevice()
	{
		sprite->OnLostDevice();
		font.OnLostDevice();
	}

	bool Graphics::IsDeviceLost()
	{
		HRESULT hr = d3dDevice->TestCooperativeLevel();

		if(hr == D3DERR_DEVICELOST)
		{
			Sleep(20);
			return true;
		}
		else if(hr == D3DERR_DRIVERINTERNALERROR)
		{
			gApp->AppError(true, "Driver Error... exiting");
			return true;
		}
		else if(hr == D3DERR_DEVICENOTRESET)
		{
			OnLostDevice();
			HR(d3dDevice->Reset(&d3dPP));
			OnResetDevice();
			return false;
		}
		else
			return false;
	}

	void Graphics::FullScreen(bool enable)
	{
		// Switch to fullscreen mode.
		if( enable )
		{
			// Are we already in fullscreen mode?
			if( !d3dPP.Windowed ) 
				return;

			int width  = GetSystemMetrics(SM_CXSCREEN);
			int height = GetSystemMetrics(SM_CYSCREEN);

			d3dPP.BackBufferFormat = D3DFMT_X8R8G8B8;
			d3dPP.BackBufferWidth  = width;
			d3dPP.BackBufferHeight = height;
			d3dPP.Windowed         = false;

			// Change the window style to a more fullscreen friendly style.
			SetWindowLongPtr(hWnd, GWL_STYLE, WS_POPUP);

			// If we call SetWindowLongPtr, MSDN states that we need to call
			// SetWindowPos for the change to take effect.  In addition, we 
			// need to call this function anyway to update the window dimensions.
			SetWindowPos(hWnd, HWND_TOP, 0, 0, width, height, SWP_NOZORDER | SWP_SHOWWINDOW);	
		}
		// Switch to windowed mode.
		else
		{
			// Are we already in windowed mode?
			if( d3dPP.Windowed ) 
				return;

			RECT R = {0, 0, 800, 600};
			AdjustWindowRect(&R, WS_OVERLAPPEDWINDOW, false);
			d3dPP.BackBufferFormat = D3DFMT_UNKNOWN;
			d3dPP.BackBufferWidth  = 800;
			d3dPP.BackBufferHeight = 600;
			d3dPP.Windowed         = true;

			// Change the window style to a more windowed friendly style.
			SetWindowLongPtr(hWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW);

			// If we call SetWindowLongPtr, MSDN states that we need to call
			// SetWindowPos for the change to take effect.  In addition, we 
			// need to call this function anyway to update the window dimensions.
			SetWindowPos(hWnd, HWND_TOP, 100, 100, R.right, R.bottom, SWP_NOZORDER | SWP_SHOWWINDOW);
		}

		// Reset the device with the changes.
		//OnLostDevice();
		//HR(d3dDevice->Reset(&d3dPP));
		//OnResetDevice();
	}


	IDirect3D9* Graphics::GetDirect3DCOM() const
	{
		return d3dObject;
	}

	IDirect3DDevice9* Graphics::GetDeviceCOM() const
	{
		return d3dDevice;
	}
	ID3DXSprite* Graphics::GetSpriteCOM() const
	{
		return sprite;
	}

	bool Graphics::SetMode(HWND hWnd, bool windowed, bool useZBuffer, int width, int height, char BPP)
	{

		if(hWnd == NULL)
			return false;
		this->hWnd = hWnd;

		if(this->d3dObject == NULL)
			return false;

		//this updates Display Mode with default display mode
		if(FAILED(d3dObject->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3dDisplayMode)))
			return false;

		this->windowed = windowed;
		zBufferEnabled = useZBuffer;

		D3DFORMAT format, altFormat;
		RECT wndRect, clientRect;
		int	wndWidth, wndHeight;
		float aspect;

		if(width > 0 && height > 0)
		{
			if(windowed == false)
			{
				this->width = d3dDisplayMode.Width;
				this->height = d3dDisplayMode.Height;
			}
			else
			{
				GetClientRect(hWnd, &clientRect);
				this->width = clientRect.right - clientRect.left;
				this->height = clientRect.bottom - clientRect.top;
			}
		}
		else
		{
			this->width = width;
			this->height = height;
		}

		this->BPP = BPP;
		if(BPP != 0 || windowed == true)
		{
			this->BPP = GetFormatBPP(d3dDisplayMode.Format);
			if(this->BPP == NULL)
				return false;
		}

		if(windowed == true)
		{
			GetWindowRect(this->hWnd, &wndRect);
			GetClientRect(this->hWnd, &clientRect);

			wndWidth = (wndRect.right - (clientRect.right - this->width)) - wndRect.left;
			wndHeight = (wndRect.bottom - (clientRect.bottom - this->height)) - wndRect.top;
			MoveWindow(this->hWnd, wndRect.left, wndRect.top, wndWidth, wndHeight, TRUE);
		}

		// Clear presentation structure
		ZeroMemory(&d3dPP, sizeof(D3DPRESENT_PARAMETERS));

		isHAL = false;

		if(windowed == true)
		{
			d3dPP.Windowed = TRUE;
			d3dPP.SwapEffect = D3DSWAPEFFECT_DISCARD;
			d3dPP.BackBufferFormat = d3dDisplayMode.Format;

			//check if HAL is supported
			if(CheckFormat(d3dDisplayMode.Format, true, true) == TRUE)
				isHAL = true;
			else
			{
				//check to see if emulated HAL is supported
				if(CheckFormat(d3dDisplayMode.Format, true, false) == FALSE)
					return false;
			}
		}
		else
		{
			d3dPP.Windowed = FALSE;
			d3dPP.SwapEffect = D3DSWAPEFFECT_FLIP;
			d3dPP.BackBufferWidth = this->width;
			d3dPP.BackBufferHeight = this->height;
			d3dPP.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
			d3dPP.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT;

			if(BPP == 32)
			{
				format = D3DFMT_X8R8G8B8;
				altFormat = D3DFMT_X8R8G8B8;
			}
			else if(BPP == 24)
			{
				format = D3DFMT_R8G8B8;
				altFormat = D3DFMT_R8G8B8;
			}
			else if(BPP == 16)
			{
				format = D3DFMT_R5G6B5;
				altFormat = D3DFMT_X1R5G5B5;
			}
			else if(BPP == 8)
			{
				format = D3DFMT_P8;
				altFormat = D3DFMT_P8;
			}

			//check new format with HAL
			if(CheckFormat(format, false, true) == true)
				isHAL = true;
			else
			{
				//new format failed, check alt with HAL
				if(CheckFormat(altFormat, false, true) == true)
				{
					isHAL = true;
					format = altFormat;
				}
				else
				{
					//Check new format with emulated HAL
					if(CheckFormat(format, false, false) == false)
					{
						//Check altFormat with emualted HAL
						if(CheckFormat(altFormat, false, false) == false)
							return false;
						else
							format = altFormat;
					}
				}
			}

			d3dPP.BackBufferFormat = format;
		}
		bool zBuffer = useZBuffer;
		//use 16bit Zbuffer
		if(zBuffer == true)
		{
			d3dPP.EnableAutoDepthStencil = true;
			d3dPP.AutoDepthStencilFormat = D3DFMT_D16;
		}
		else
		{
			d3dPP.EnableAutoDepthStencil = false;
		}

		if(FAILED(d3dObject->CreateDevice(D3DADAPTER_DEFAULT, (isHAL ? D3DDEVTYPE_HAL : D3DDEVTYPE_REF),
			hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dPP, &d3dDevice)))
		{
			if(zBuffer == true)
			{
				zBuffer = false;
				d3dPP.EnableAutoDepthStencil = FALSE;

				if(FAILED(d3dObject->CreateDevice(D3DADAPTER_DEFAULT, (isHAL ? D3DDEVTYPE_HAL : D3DDEVTYPE_REF),
					hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dPP, &d3dDevice)))
					return false;
			}
			else
				return false;
		}



		//Device now created, set the default render states
		EnableLighting(false);
		EnableZBuffer(zBuffer);
		EnableAlphaBlending(false);
		EnableAlphaTesting(false);

		HR(d3dDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE));
		HR(d3dDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE));
		HR(d3dDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE));
		//HR(d3dDevice->SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2));

		HR(d3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR));
		HR(d3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR));
		HR(d3dDevice->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR));

		SetAmbientLight(255, 255, 255);

		aspect = (float)this->height / (float)this->width;
		SetPerspective(D3DX_PI*.25f, aspect, 1.0f, 10000.f);

		if(FAILED(D3DXCreateSprite(d3dDevice, &sprite)))
			return false;

		font.Create(this, sprite, "Arial");

		return true;
	}

	bool Graphics::Shutdown()
	{
		ReleaseCOM(d3dObject);
		ReleaseCOM(d3dDevice);
		ReleaseCOM(sprite);

		return true;
	}

	UINT Graphics::GetNumDisplayModes(D3DFORMAT format)
	{
		if(d3dObject == NULL)
			return 0;

		return d3dObject->GetAdapterModeCount(D3DADAPTER_DEFAULT, format);
	}

	bool Graphics::GetDisplayModeInfo(UINT num, D3DDISPLAYMODE *mode, D3DFORMAT format)
	{
		UINT max;

		if(d3dObject == NULL)
			return false;

		max = GetNumDisplayModes(format);
		if(num >= max)
			return false;

		return SUCCEEDED(d3dObject->EnumAdapterModes(D3DADAPTER_DEFAULT, format, num, mode));
	}

	char Graphics::GetFormatBPP(D3DFORMAT format)
	{
		switch(format) 
		{
			// 32 bit modes
		case D3DFMT_A8R8G8B8:
		case D3DFMT_X8R8G8B8:
			return 32;
			break;

			// 24 bit modes
		case D3DFMT_R8G8B8:
			return 24;
			break;

			// 16 bit modes
		case D3DFMT_R5G6B5:
		case D3DFMT_X1R5G5B5:
		case D3DFMT_A1R5G5B5:
		case D3DFMT_A4R4G4B4:
			return 16;
			break;

			// 8 bit modes
		case D3DFMT_A8P8:
		case D3DFMT_P8:
			return 8;
			break;

		default:
			return 0;
		}
	}

	bool Graphics::CheckFormat(D3DFORMAT format, bool windowed, bool HAL)
	{
		return SUCCEEDED(d3dObject->CheckDeviceType(D3DADAPTER_DEFAULT, (HAL ? D3DDEVTYPE_HAL : D3DDEVTYPE_REF),
			format, format, windowed));	
	}

	bool Graphics::BeginScene()
	{
		if(d3dDevice == NULL)
			return false;

		D3DXMATRIX mat;
		D3DXMatrixIdentity(&mat);
		sprite->SetTransform(&mat);
		return SUCCEEDED(d3dDevice->BeginScene());
	}

	bool Graphics::EndScene()
	{
		if(d3dDevice == NULL)
			return false;

		for(DWORD i = 0; i < 8; ++i)
			d3dDevice->SetTexture(i, NULL);

		return SUCCEEDED(d3dDevice->EndScene());
	}

	void Graphics::DrawFPS()
	{
		if(drawFPS)
		{
			
			char fps[20];
			wsprintf(fps, "FPS: %d\n", FPS);
			COUT(fps);
			font.Print(fps, 5, 5, 200, 100, D3DCOLOR_XRGB(255,255,255));
		}
	}

	bool Graphics::BeginSprite()
	{
		if(sprite == NULL)
			return false;

		return SUCCEEDED(sprite->Begin(D3DXSPRITE_OBJECTSPACE|D3DXSPRITE_DONOTMODIFY_RENDERSTATE));
	}

	bool Graphics::EndSprite()
	{
		if(sprite == NULL)
			return false;

		return SUCCEEDED(sprite->End());
	}

	bool Graphics::Draw()
	{
		if(d3dDevice == NULL)
			return false;

		return SUCCEEDED(d3dDevice->Present(NULL, NULL, NULL, NULL));
	}

	bool Graphics::Clear(long color, float zBuffer)
	{
		if(d3dDevice == NULL)
			return false;

		if(zBufferEnabled == false)
			return ClearDisplay(color);

		return SUCCEEDED(d3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, color, zBuffer, 0));
	}

	bool Graphics::ClearDisplay(long color)
	{
		if(d3dDevice == NULL)
			return false;

		return SUCCEEDED(d3dDevice->Clear(0, NULL, D3DCLEAR_TARGET, color, 1.0f, 0));
	}

	bool Graphics::ClearZBuffer(float zBuffer)
	{
		if(d3dDevice == NULL || d3dPP.EnableAutoDepthStencil == FALSE)
			return false;

		return SUCCEEDED(d3dDevice->Clear(0, NULL, D3DCLEAR_ZBUFFER, 0, zBuffer, 0));
	}

	int Graphics::GetBackBufferWidth() const
	{
		return this->width;
	}

	int Graphics::GetBackBufferHeight() const
	{
		return this->height;
	}

	char Graphics::GetBPP() const
	{
		return BPP;
	}

	bool Graphics::GetHAL() const
	{
		return isHAL;
	}

	bool Graphics::GetZBuffer() const
	{
		return zBufferEnabled;
	}

	bool Graphics::SetCamera(Camera *camera)
	{
		if(d3dDevice == NULL || camera == NULL)
			return false;

		return SUCCEEDED(d3dDevice->SetTransform(D3DTS_VIEW, camera->GetMatrix()));
	}

	bool Graphics::SetWorldPosition(WorldPosition *worldPos)
	{
		if(d3dDevice == NULL || worldPos == NULL)
			return false;

		return SUCCEEDED(d3dDevice->SetTransform(D3DTS_WORLD, worldPos->GetMatrix()));
	}

	bool Graphics::SetLight(UINT num, Light *light)
	{
		if(d3dDevice == NULL || light == NULL)
			return false;

		return SUCCEEDED(d3dDevice->SetLight(num, light->GetLight()));
	}

	bool Graphics::SetAmbientLight(BYTE red, BYTE green, BYTE blue)
	{
		if(d3dDevice == NULL)
			return false;

		ambientRed = red;
		ambientGreen = green;
		ambientBlue = blue;
		D3DCOLOR color = D3DCOLOR_XRGB(red, green, blue);

		return SUCCEEDED(d3dDevice->SetRenderState(D3DRS_AMBIENT, color));
	}

	bool Graphics::GetAmbientLight(BYTE *red, BYTE *green, BYTE *blue)
	{
		if(red)
			*red = ambientRed;
		if(green)
			*green = ambientGreen;
		if(blue)
			*blue = ambientBlue;

		return true;
	}

	bool Graphics::SetMaterial(Material *material)
	{
		if(d3dDevice == NULL)
			return false;

		if(material)
			return SUCCEEDED(d3dDevice->SetMaterial(material->GetMaterial()));

		return true;
	}

	bool Graphics::SetTexture(USHORT num, Texture *texture)
	{
		if(d3dDevice == NULL || num > 7)
			return false;

		if(texture)
		{
			return SUCCEEDED(d3dDevice->SetTexture(num, texture->GetTextureCOM()));
		}

		return SUCCEEDED(d3dDevice->SetTexture(num, NULL));
	}

	bool Graphics::SetPerspective(float FOV, float aspect, float n, float f)
	{
		if(d3dDevice == NULL)
			return false;

		D3DXMATRIX projMatrix;

		D3DXMatrixPerspectiveFovLH(&projMatrix, FOV, aspect, n, f);

		return SUCCEEDED(d3dDevice->SetTransform(D3DTS_PROJECTION, &projMatrix));
	}

	D3DXMATRIX Graphics::GetPersective()
	{
		D3DXMATRIX projMatrix;
		d3dDevice->GetTransform(D3DTS_PROJECTION, &projMatrix);
		return projMatrix;
	}

	bool Graphics::EnableLight(DWORD num, BOOL enable)
	{
		if(d3dDevice== NULL)
			return false;

		return SUCCEEDED(d3dDevice->LightEnable(num, enable));
	}

	bool Graphics::EnableLighting(BOOL enable)
	{
		if(d3dDevice == NULL)
			return false;

		return SUCCEEDED(d3dDevice->SetRenderState(D3DRS_LIGHTING, enable));
	}

	bool Graphics::EnableZBuffer(BOOL enable)
	{
		if(d3dDevice == NULL)
			return false;

		zBufferEnabled = (enable ? true : false);
		return SUCCEEDED(d3dDevice->SetRenderState(D3DRS_ZENABLE, (enable ? D3DZB_TRUE : D3DZB_FALSE)));
	}

	bool Graphics::EnableAlphaBlending(BOOL enable, DWORD source, DWORD dest)
	{
		if(d3dDevice == NULL)
			return false;

		if(FAILED(d3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, enable)))
			return false;

		if(enable)
		{
			HR(d3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE));
			HR(d3dDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1));
			HR(d3dDevice->SetRenderState(D3DRS_SRCBLEND, source));
			HR(d3dDevice->SetRenderState(D3DRS_DESTBLEND, dest));
		}

		return true;
	}

	bool Graphics::EnableAlphaTesting(BOOL enable)
	{
		if(d3dDevice == NULL)
			return false;

		if(FAILED(d3dDevice->SetRenderState(D3DRS_ALPHATESTENABLE, enable)))
			return false;

		if(enable)
		{
			d3dDevice->SetRenderState(D3DRS_ALPHAREF, 10);
			d3dDevice->SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL);
		}

		return true;
	}

	bool Graphics::CheckDeviceCaps()
	{
		return true;
	}

	void Graphics::Update(float dt)
	{
		if(drawFPS)
		{
			fpsTimer += dt;
			frameCount++;
			if(fpsTimer >= 1)
			{
				FPS = frameCount;
				frameCount = 0;
				fpsTimer = 0;
			}
		}
	}
}